Skip to content

Add operation components, orchestrator, and emitter rendering#84

Closed
FionaBronwen wants to merge 29 commits into
fionabronwen/graphql-components-2from
fionabronwen/graphql-components-operations-wiring
Closed

Add operation components, orchestrator, and emitter rendering#84
FionaBronwen wants to merge 29 commits into
fionabronwen/graphql-components-2from
fionabronwen/graphql-components-operations-wiring

Conversation

@FionaBronwen

@FionaBronwen FionaBronwen commented Apr 21, 2026

Copy link
Copy Markdown

Summary

  • Operation components: QueryType, MutationType, SubscriptionType (in components/operations/) render the three GraphQL root operation types
  • Orchestrator: components/type-collections.tsx dispatches each classified-type bucket (scalars, enums, unions, interfaces, objects, inputs) into the appropriate leaf-type component
  • Emitter wiring: rename src/emitter.tssrc/emitter.tsx. Phase 5 now renders via Alloy's renderSchema, converts to SDL with graphql-js printSchema, and writes the output via emitFile. Adds output-file option handling with interpolatePath.
  • Component tests: test/components/operation-types.test.tsx with 10 tests covering QueryType, MutationType, and SubscriptionType rendering
  • E2E tests: test/e2e.test.ts and test/emitter.test.ts with snapshot assertions proving the full TypeSpec → mutation → classification → rendering → SDL pipeline works

Test plan

@FionaBronwen FionaBronwen force-pushed the fionabronwen/graphql-components-2 branch from 79f53c2 to 41fbefb Compare April 21, 2026 20:25
@FionaBronwen FionaBronwen force-pushed the fionabronwen/graphql-components-operations-wiring branch 2 times, most recently from 3387127 to 1a5163c Compare April 21, 2026 20:33
@FionaBronwen FionaBronwen force-pushed the fionabronwen/graphql-components-2 branch from 41fbefb to 8a187c4 Compare April 21, 2026 20:43
@FionaBronwen FionaBronwen force-pushed the fionabronwen/graphql-components-operations-wiring branch 2 times, most recently from 9e90b36 to ffc3f0f Compare April 22, 2026 17:09
@FionaBronwen FionaBronwen force-pushed the fionabronwen/graphql-components-2 branch 2 times, most recently from ffa9c95 to fe7da60 Compare April 28, 2026 15:13
@FionaBronwen FionaBronwen force-pushed the fionabronwen/graphql-components-operations-wiring branch from ffc3f0f to 2763d30 Compare April 28, 2026 15:13
Introduce the foundation for the component-based GraphQL emitter:

- Build/config: Add @alloy-js/core, @alloy-js/graphql dependencies,
  configure JSX transpilation (tsconfig, vitest)
- Context system: GraphQLSchemaContext with ClassifiedTypes,
  ModelVariants, and ScalarVariant interfaces
- Field components: Field, OperationField, and GraphQLTypeExpression
  for rendering model properties and operations as GraphQL SDL
- Type resolution: GraphQLTypeExpression handles scalars, models,
  enums, unions, arrays, and nullability using an isInput prop to
  distinguish input vs output context
- Scalar mappings: Add getGraphQLBuiltinName() for built-in scalar
  identity checks
@FionaBronwen FionaBronwen force-pushed the fionabronwen/graphql-components-operations-wiring branch 2 times, most recently from f73791c to ce4affb Compare April 28, 2026 15:55
@FionaBronwen FionaBronwen force-pushed the fionabronwen/graphql-components-2 branch from 618e444 to 1f7438a Compare April 28, 2026 20:56
@FionaBronwen FionaBronwen force-pushed the fionabronwen/graphql-components-operations-wiring branch from ce4affb to 98fb4f4 Compare April 28, 2026 21:21
@FionaBronwen FionaBronwen force-pushed the fionabronwen/graphql-components-2 branch 2 times, most recently from 2ef9793 to db9b099 Compare April 30, 2026 21:10
@FionaBronwen FionaBronwen force-pushed the fionabronwen/graphql-components-operations-wiring branch from 98fb4f4 to eb57da4 Compare April 30, 2026 21:10
The ModelVariants lookup structure was originally designed to help
components decide when to append "Input" suffix to model names.
However, the mutation engine now fully handles this:
- Input models are mutated with the "Input" suffix already applied
- Property type references are rewired to point to the correct variants

This commit removes the unused ModelVariants interface and the lookup
logic from GraphQLTypeExpression, simplifying the component to just
use the model's name directly.

Also adds unionMembers to context (needed for union rendering).
@FionaBronwen FionaBronwen force-pushed the fionabronwen/graphql-components-2 branch from db9b099 to 6cd9324 Compare May 1, 2026 15:17
@FionaBronwen FionaBronwen force-pushed the fionabronwen/graphql-components-operations-wiring branch from eb57da4 to 2be2c50 Compare May 1, 2026 15:17
Remove unused utility functions that were superseded by the mutation engine:
- getTemplatedModelName (mutation engine handles template naming)
- getSingleNameWithNamespace (never used in production)
- isArray, isRecordType, isScalarOrEnumArray, isUnionArray (use compiler's isArrayModelType)
- unwrapModel, unwrapType (never imported)
- isTrueModel (never imported)

Also removes the corresponding test for getSingleNameWithNamespace.
@FionaBronwen FionaBronwen force-pushed the fionabronwen/graphql-components-2 branch from 6cd9324 to 671900f Compare May 1, 2026 15:33
@FionaBronwen FionaBronwen force-pushed the fionabronwen/graphql-components-operations-wiring branch from 2be2c50 to c975212 Compare May 1, 2026 15:33
@FionaBronwen FionaBronwen force-pushed the fionabronwen/graphql-components-2 branch 2 times, most recently from 255e13b to 15743c4 Compare May 1, 2026 19:18
@FionaBronwen FionaBronwen force-pushed the fionabronwen/graphql-components-operations-wiring branch 2 times, most recently from 918f6cc to cf1eb14 Compare May 1, 2026 20:04
@FionaBronwen FionaBronwen marked this pull request as ready for review May 5, 2026 15:27
Removes the procedural GraphQL emitter and stands up the data-pipeline
skeleton that the upcoming Alloy component-based emitter will build on.

Deleted:
- graphql-emitter.ts (old procedural emitter)
- schema-emitter.ts (old schema-specific emitter)
- registry.ts (old type registry)
- type-maps.ts (old type mapping logic)
- test/emitter.test.ts tests for the legacy emitter

Rewrote src/emitter.ts as a four-phase data pipeline:
- Phase 1: type usage tracking (reachability / input-output marking)
- Phase 2: mutation (GraphQL naming via the mutation engine)
- Phase 3: classification (interfaces, output models, input models, ops)
- Phase 4: model variant lookups

The pipeline produces a SchemaPipelineResult bundle
(classifiedTypes, modelVariants, scalarSpecifications) that is handed
to a renderSchema stub. The stub is a no-op in this PR; component-based
SDL rendering, file emission, and the .tsx conversion land in follow-up
PRs. Introducing the SchemaPipelineResult type and the pipeline/renderer
seam here keeps the downstream PR reviewable on its own.

Also introduces two new diagnostics that the pipeline reports today:
- empty-schema: fires when a schema has no query root (GraphQL requires one)
- void-operation-return: fires when an operation returns void (no GraphQL
  equivalent; the operation is filtered out of the schema)

Test coverage added for both diagnostics in test/emitter.test.ts.
The emitter used to walk the schema namespace itself, bucket types by kind,
dedup scalars, and run a second classification pass to split models into
input/output variants. That loop also leaked implementation details (an
`originalToMutated` back-map and a `TypeUsageResolver` that outlived
mutation) into the emitter's public shape.

Introduce `GraphQLMutationEngine.mutateSchema(schema, typeUsage)` that
returns a fully-classified `MutatedSchema`:

- Pre-bucketed models: `interfaces`, `outputModels`, `inputModels`
- Pre-classified operations: `queries`, `mutations`, `subscriptions`
- Derived metadata: `wrapperModels`, `scalarVariants`, `scalarSpecifications`

Classification happens inline during the walk. `typeUsage` is consumed
entirely inside the engine; the emitter hands it over and never touches
it again. `originalToMutated` no longer exists outside the engine.

Implementation lives in `src/mutation-engine/schema-mutator.ts` as a free
function called from the `GraphQLMutationEngine` class to avoid a circular
import between engine.ts and schema-mutator.ts.

The emitter's `emitSchema` reduces to: resolve type usage, call
`engine.mutateSchema`, report void-return diagnostics, check for empty
schema, build variant lookups, hand off to the (stubbed) renderer.
After the TypeGraph refactor in PR #77, ScalarVariant was removed from the
context module. Move it to schema-mutator.ts where it belongs since it's
part of the MutatedSchema interface.
Add the first set of Alloy JSX components for GraphQL type emission:
- EnumType: renders GraphQL enum definitions with member descriptions and deprecation
- ScalarType: renders custom scalar definitions with @specifiedBy support
- UnionType: renders union type definitions with model and scalar variant members
- GraphQLSchema: root context wrapper providing TspContext and GraphQLSchemaContext

Add component-level tests using renderSchema + printSchema to exercise the
real Alloy rendering pipeline. Each component is tested in isolation with a
lightweight test helper (renderComponentToSDL) that provides minimal context.

14 new tests covering: basic rendering, doc comments, deprecation, name
sanitization, @specifiedBy, union member registration, and scalar wrappers.
- Extract isScalarLikeType() to type-utils.ts shared utility, replacing
  duplicated inline check in union-type.tsx
- Replace unsafe `as Union` casts in union-type tests with assertUnionResult()
  helper that provides a clear error if the mutation returns a Model
Replace fragile .toContain() assertions with inline snapshots to match
TSP ecosystem best practices. This makes test output more readable and
maintainable by showing the complete expected SDL in one place.
The function was extracted from union-type.tsx in commit 6d47132e4
but the type-utils.ts hunk was dropped during the rebase due to
conflicts with 'Remove dead code from type-utils.ts' in the base.
- ScalarType: take specificationUrl as prop instead of from context
- component-test-utils: use minimal TypeGraph context
- scalar-type.test: pass specificationUrl as prop
- Mutate operations in schema-mutator so type references point to
  correctly mutated types (input params → mutated input models,
  return types → mutated output models)
- Fix nullable union replacement to mutate inner type with context
  (Address | null → AddressInput in input context)
- Add Input suffix to models in input context with double-suffix
  prevention (BookInput stays BookInput, not BookInputInput)
- Add comprehensive tests for input suffix, nullable union
  replacement, and operation type references
Models used as both input and output must be mutated separately:
- Output context produces 'Foo'
- Input context produces 'FooInput'

Previously, all models were mutated with Output context only, causing
duplicate type names when rendering both variants.
- Add withInputSuffix helper to prevent "PetInput" → "PetInputInput"
- Fix oneOf input unions to mutate variant types with input context
  (model variants now correctly get their Input suffix, e.g. Cat → CatInput)
- Refactor model.ts to use shared withInputSuffix helper
Add field-bearing type components that use the Field infrastructure
(already present from the parent branch):
- ObjectType: renders object types with fields, @compose interfaces,
  and @operationFields support
- InterfaceType: renders interface type definitions with fields
- InputType: renders input types with automatic Input suffix when a
  model appears in both input and output positions

17 new component tests covering: basic field rendering, doc comments,
optional/nullable fields, array/list types, deprecated fields,
interface implementation via @compose, and Input suffix logic.
…ed imports

- Add explanatory comment on getComposition/iface.name in object-type.tsx
  clarifying that pre-mutation names match post-mutation names (mutation
  engine doesn't rename models)
- Remove unused `type Model` import from all G2 test files
- Add @operationFields test to ObjectType (verifies operation field rendering)
- Add nested model reference test to ObjectType (verifies type references)
- Add empty model tests to ObjectType, InputType, InterfaceType
  (documents that GraphQL requires at least one field per type)
The mutation engine now handles all input/output type naming, so components
can use type.name directly without looking up whether a model has both
input and output variants.
- Remove implementation detail comments about mutation engine
- Remove redundant tests that were testing non-responsibility of the component
  (InputType doesn't add suffixes - that's not its job)
Replace fragile .toContain() assertions with inline snapshots to match
TSP ecosystem best practices. This makes test output more readable and
maintainable by showing the complete expected SDL in one place.
Brings the component-based GraphQL emitter online by wiring the data
pipeline from the foundation skeleton into Alloy JSX rendering.

New components:
- components/operations/query-type.tsx, mutation-type.tsx, subscription-type.tsx:
  render the three GraphQL root operation types
- components/operations/index.ts: barrel export
- components/type-collections.tsx: orchestrator that dispatches each
  classified-type bucket (scalars, enums, unions, interfaces, objects, inputs)
  into the appropriate leaf-type component

Emitter wiring:
- Rename src/emitter.ts → src/emitter.tsx
- Phase 5 now renders via Alloy's renderSchema, converts to SDL with
  graphql-js printSchema, and writes the output via emitFile
- Adds output-file option handling with interpolatePath

Testing:
- test/e2e.test.ts: single happy-path smoke test proving the full
  TypeSpec → mutation → classification → rendering → SDL pipeline works
- Update the existing test/emitter.test.ts data-pipeline test to now
  assert that SDL output is produced (Phase 5 is wired up)

Broader integration coverage (nullability, input/output splitting,
unions, enums, arrays, circular refs, deprecation, doc comments) lands
in a follow-up PR focused on tests.
Adds component tests for QueryType, MutationType, and SubscriptionType
using inline snapshots. Tests cover rendering with scalar return types,
model return types (with stub type registration), parameters, and empty
operation lists.

Updates renderComponentToSDL utility to support skipPlaceholderQuery
option for testing QueryType, while maintaining backwards compatibility
with existing tests that pass context overrides directly.

Also cleans up e2e and emitter tests to use consistent vitest expect()
assertions with toMatchInlineSnapshot() instead of mixed strictEqual
and .includes() checks.
- type-collections: components take data via props instead of context
- emitter: pass MutatedSchema data directly to collection components
- Use minimal TypeGraph context (just globalNamespace)

This aligns with the TypeGraph design where context is minimal and
components receive their data through props.
@FionaBronwen FionaBronwen force-pushed the fionabronwen/graphql-components-2 branch from ccdc78f to 372c7da Compare June 3, 2026 20:01
@FionaBronwen FionaBronwen force-pushed the fionabronwen/graphql-components-operations-wiring branch from 8ac0071 to c42edc5 Compare June 3, 2026 20:01
@FionaBronwen FionaBronwen force-pushed the fionabronwen/graphql-components-2 branch from 372c7da to c63fd39 Compare June 9, 2026 20:56
@swatkatz swatkatz force-pushed the fionabronwen/graphql-components-2 branch from c63fd39 to 16e9fe3 Compare June 10, 2026 15:30
@swatkatz swatkatz deleted the branch fionabronwen/graphql-components-2 June 10, 2026 15:58
@swatkatz swatkatz closed this Jun 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants